using System;
using System.IO;
using System.Net;
using System.Web.Services.Protocols;
using System.Windows.Forms;
using System.Xml;
using CodePlex.DependencyInjection;
using CodePlex.TfsLibrary;
using CodePlex.TfsLibrary.ClientEngine;
using CodePlex.TfsLibrary.ObjectModel;
using CodePlex.TfsLibrary.RegistrationWebSvc;
using CodePlex.TfsLibrary.RepositoryWebSvc;
using CodePlex.TfsLibrary.Utility;

namespace CodePlex.CodePlexClientLibrary
{
    public abstract class ApplicationBase<TDefaultCommand>
        where TDefaultCommand : ICommand
    {
        static string ExtractMessageFromSoapException(SoapException ex)
        {
            const string soapMessage = "Server was unable to process request. ---> ";
            const string typePrefix = "System.Web.Services.Protocols.SoapException: ";

            int index = ex.Message.IndexOf(soapMessage);

            if (index > -1)
                return GetFirstLine(ex.Message.Substring(index + soapMessage.Length));

            index = ex.Message.IndexOf(typePrefix);

            if (index > -1)
                return GetFirstLine(ex.Message.Substring(index + typePrefix.Length));

            return null;
        }

        static ICommand GetDefaultCommand(IObjectFactory container)
        {
            return container.Get<TDefaultCommand>();
        }

        static string GetFirstLine(string text)
        {
            int index = text.IndexOf('\n');

            if (index > -1)
                return text.Substring(0, index);

            return text;
        }

        static string GetSoapExceptionMessage(SoapException ex)
        {
            if (ex.Detail != null)
            {
                XmlNode messageNode = ex.Detail.SelectSingleNode("exception/message");
                if (messageNode != null)
                    return messageNode.InnerText;
            }

            return null;
        }

        static void OnUnhandledException(Exception ex)
        {
            if (ex is SoapException)
                PrintSoapException((SoapException)ex);
            else if (ex is ConflictedCommitException ||
                     ex is InvalidTfsDirectoryStructureException ||
                     ex is CommandLineException ||
                     ex is TfsFailureException ||
                     ex is TfsStateException ||
                     ex is InconsistentTfsStateException ||
                     ex is NoAccessToProjectException ||
                     ex is WebException ||
                     ex is NetworkAccessDeniedException ||
                     ex is IOException)
                PrintMessage(ex.Message);
            else
            {
                Console.WriteLine("Unhandled exception:");
                PrintExceptionRecursive(ex);
            }
        }

        static void PrintExceptionRecursive(Exception ex)
        {
            Console.WriteLine("{0}: {1}", ex.GetType().FullName, ex.Message);
            Console.WriteLine(ex.StackTrace);

            if (ex.InnerException != null)
                PrintExceptionRecursive(ex.InnerException);
        }

        static void PrintMessage(string message)
        {
            Console.WriteLine("error: {0}", message);
        }

        static void PrintSoapException(SoapException ex)
        {
            string message = GetSoapExceptionMessage(ex);

            if (String.IsNullOrEmpty(message))
                message = ExtractMessageFromSoapException(ex);

            if (String.IsNullOrEmpty(message))
                message = ex.Message;

            PrintMessage(message);
        }

        protected abstract void RegisterCommands(CommandLineHelper helper);

        public int Run()
        {
            ServicePointManager.DefaultConnectionLimit = 15;

            try
            {
                Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException, false);
            }
            catch (InvalidOperationException) {}

            Application.EnableVisualStyles();

            try
            {
                using (DependencyContainer container = new DependencyContainer())
                {
                    CommandLineHelper helper = new CommandLineHelper(container);
                    container.RegisterSingletonInstance(helper);
                    SetupContainer(container);
                    RegisterCommands(helper);
                    Run(container);
                    return 0;
                }
            }
            catch (Exception ex)
            {
                OnUnhandledException(ex);
                return 1;
            }
        }

        static void Run(IObjectFactory container)
        {
            CommandLineParser parser = container.Get<CommandLineParser>();
            ICommand command = parser.ParseCommandLine(Environment.CommandLine) ?? GetDefaultCommand(container);

            if (parser.ArgCountTooLow(command))
                Console.WriteLine("error: Not enough arguments provided.");
            else if (parser.ArgCountTooHigh(command))
                Console.WriteLine("error: Too many arguments provided.");
            else
                command.Execute();
        }

        protected virtual void SetupContainer(DependencyContainer container)
        {
            container.RegisterTypeMapping<ICodePlexProjectInfoService, CodePlexProjectInfoService>();
            container.RegisterTypeMapping<IFileSystem, FileSystem>();
            container.RegisterTypeMapping<IIgnoreList, IgnoreList>();
            container.RegisterTypeMapping<IPatchEngine, PatchEngine>();
            container.RegisterTypeMapping<IRegistrationService, RegistrationService>();
            container.RegisterTypeMapping<IRegistrationWebSvcFactory, RegistrationWebSvcFactory>();
            container.RegisterTypeMapping<IRepositoryWebSvcFactory, RepositoryWebSvcFactory>();
            container.RegisterTypeMapping<ISourceControlService, SourceControlService>();
            container.RegisterTypeMapping<ITfsEngine, TfsEngine>();
            container.RegisterTypeMapping<IWebTransferService, WebTransferService>();

            container.CacheInstancesOf<CommandLineHelper>();

            CredentialsCache cache = new CredentialsCache(container.Get<IFileSystem>());
            container.RegisterSingletonInstance<ICredentialsCache>(cache);
        }
    }
}